package benchmarks;

import edu.vub.at.actors.eventloops.Callable;
import edu.vub.at.actors.natives.ELActor;
import edu.vub.at.actors.natives.ELVirtualMachine;
import edu.vub.at.actors.natives.SharedActorField;
import edu.vub.at.eval.Evaluator;
import edu.vub.at.exceptions.InterpreterException;
import edu.vub.at.objects.ATObject;
import edu.vub.at.objects.ATTable;
import edu.vub.at.objects.mirrors.NativeClosure;
import edu.vub.at.objects.natives.grammar.AGSymbol;
import edu.vub.at.parser.NATParser;

public class DistributionBenchmarkCases {
	private ELVirtualMachine virtual1_;
	private ELVirtualMachine virtual2_;
	private ELVirtualMachine virtual3_;

	private static final String _TEST_GROUP_NAME_ = "AmbientTalkTest";

	private static final int _TIMEOUT_ = 10000;

	private boolean testResult_ = false;

	// used to avoid 'final' restriction for nested classes
	protected synchronized void setTestResult(boolean value) {
		testResult_ = value;

		// typically, we wait for a given timeout but can resume earlier when
		// this event takes place
		this.notify();
	}

	// used to avoid 'final' restriction for nested classes
	protected boolean getTestResult() {
		return testResult_;
	}

	public void setUp() throws Exception {
		virtual1_ = new ELVirtualMachine(Evaluator.getNil(),
				new SharedActorField[] {}, _TEST_GROUP_NAME_,
				ELVirtualMachine._DEFAULT_IP_ADDRESS_, System.out);
		virtual2_ = new ELVirtualMachine(Evaluator.getNil(),
				new SharedActorField[] {}, _TEST_GROUP_NAME_,
				ELVirtualMachine._DEFAULT_IP_ADDRESS_, System.out);
		virtual3_ = new ELVirtualMachine(Evaluator.getNil(),
				new SharedActorField[] {}, _TEST_GROUP_NAME_,
				ELVirtualMachine._DEFAULT_IP_ADDRESS_, System.out);

	}

	public void tearDown() throws Exception {
		if (virtual1_ != null) {
			virtual1_.event_goOffline();
			virtual1_.stopProcessing();
		}

		if (virtual2_ != null) {
			virtual2_.event_goOffline();
			virtual2_.stopProcessing();
		}

		if (virtual3_ != null) {
			virtual3_.event_goOffline();
			virtual3_.stopProcessing();
		}
	}

	// Creates an ELActor, hosted on the provided VM.
	private ELActor setUpActor(ELVirtualMachine host)
			throws InterpreterException {
		return host.createEmptyActor().getFarHost();
	}

	// installs a closure in a particular actor's scope which allows signalling
	// a return value
	// with the added bonus of waking up the test thread from waiting.
	private void setUpSuccessTrigger(ELActor processor) throws Exception {
		processor.sync_event_performTest(new Callable() {
			public Object call(Object argument) throws InterpreterException {
				return Evaluator.getGlobalLexicalScope().meta_defineField(
						AGSymbol.jAlloc("success"),
						new NativeClosure(Evaluator.getNil()) {

							public ATObject base_apply(ATTable arguments)
									throws InterpreterException {
								setTestResult(true);

								return Evaluator.getNil();
							}
						});
			}
		});
	}

	// Joint code for the various test suites to test the behaviour of the AT
	// connection observers
	private void setUpConnectionObservers() throws Exception {
		ELActor subscriber = setUpActor(virtual1_);
		ELActor provider = setUpActor(virtual2_);

		// We define a closure to inform us the test succeeded
		setUpSuccessTrigger(subscriber);

		subscriber.sync_event_eval(NATParser.parse(
				"DistributionTest#setUpConnectionObservers()",
				"deftype Service; \n" + "when: Service discovered: { | ref |"
						+ "  whenever: ref disconnected: { success(); }; \n"
						+ "  whenever: ref reconnected:  { success(); }; \n" +
						// explicitly triggering success, although we are not
						// testing service discovery
						// allows to minimize the waiting time until we can go
						// offline
						"  success(); " + "} \n;"));

		provider.sync_event_eval(NATParser
				.parse("DistributionTest#setUpConnectionObservers()",
						"deftype Service; \n"
								+ "export: (object: { nil }) as: Service"));
	}

	public synchronized void testServiceDiscovery() throws Exception {

		setTestResult(false);

		setUpConnectionObservers();

		virtual1_.event_goOnline();
		virtual2_.event_goOnline();

		try {
			this.wait(_TIMEOUT_);
		} catch (InterruptedException e) {
		}
		;

	}
	
	public synchronized void testSend100() throws Exception{
		ELActor vm1 = setUpActor(virtual1_);
		ELActor vm2   = setUpActor(virtual2_);
		setUpSuccessTrigger(vm2);
		
		vm1.sync_event_eval(
				NATParser.parse("DistributionTest#testMulticast()",
						"deftype Service; \n" +
						"def far := nil;" +
						"when: Service discovered: { | ref | \n" +
						"   far := ref; \n" +
						"   100.doTimes: { |i| far <-hello(i); " +
						"}; \n" +
						"}; \n" +
						""));
		
		vm2.sync_event_eval(
				NATParser.parse("DistributionTest#testMulticast()",
						"deftype Service; \n" +
					    "def ctr := nil; \n" +
						"def test := object: { \n" +
						"	def hello( i ) { \n" +
						"                    ctr := i; \n" +
						"                     if: (ctr = 100) then: { \n" +
						"                          success(); \n" +
						"                       }; \n" +
						"                  }; \n" +
						"}; \n" +
						"export: test as: Service"));
		
		virtual1_.event_goOnline();
		virtual2_.event_goOnline();
		
		try {
			this.wait( _TIMEOUT_ );
		} catch (InterruptedException e) {};
		
	}
	
	//To the same far ref 
	public synchronized void testMulticast100() throws Exception{
		ELActor vm1 = setUpActor(virtual1_);
		ELActor vm2   = setUpActor(virtual2_);
		setUpSuccessTrigger(vm2);
		
		vm1.sync_event_eval(
				NATParser.parse("DistributionTest#testMulticast()",
						"deftype Service; \n" +
						"when: Service discovered: { | ref | \n" +
						"   def far[100] {ref}; \n" +
						"   send: (<-hello(1)) toGroup: far; \n" +
						"}; \n" +
						""));
		
		vm2.sync_event_eval(
				NATParser.parse("DistributionTest#testMulticast()",
						"deftype Service; \n" +
					    "def ctr := 0; \n" +
						"def test := object: { \n" +
						"	def hello( i ) { \n" +
						"                    ctr := (ctr + i); \n" +
						"                     if: (ctr = 100) then: { \n" +
						"                          success(); \n" +
						"                       }; \n" +
						"                  }; \n" +
						"}; \n" +
						"export: test as: Service"));
		
		virtual1_.event_goOnline();
		virtual2_.event_goOnline();
		
		try {
			this.wait( _TIMEOUT_ );
		} catch (InterruptedException e) {};
		
	}
	
	
	//Nu is met een groter object ipv vele kleine.
	public synchronized void testSendBig100() throws Exception{
		ELActor vm1 = setUpActor(virtual1_);
		ELActor vm2   = setUpActor(virtual2_);
		setUpSuccessTrigger(vm2);
		
		vm1.sync_event_eval(
				NATParser.parse("DistributionTest#testMulticast()",
						"deftype Service; \n" +
						"def far := nil;" +
					    "def bigObj := object: { \n" +
					    "   def a := 100; \n" +
					    "   def b := [1, 3, 5, \"hahaha\"]; \n" +
					    "   def c[100] {20}; \n" +
						"}; \n" +
						"when: Service discovered: { | ref | \n" +
						"   far := ref; \n" +
						"   100.doTimes: { |i| far <-hello(i, bigObj); " +
						"}; \n" +
						"}; \n" +
						""));
		
		vm2.sync_event_eval(
				NATParser.parse("DistributionTest#testMulticast()",
						"deftype Service; \n" +
					    "def ctr := nil; \n" +
						"def test := object: { \n" +
						"	def hello( i, bigObj ) { \n" +
						"                    ctr := i; \n" +
						"                     if: (ctr = 100) then: { \n" +
						"                          success(); \n" +
						"                       }; \n" +
						"                  }; \n" +
						"}; \n" +
						"export: test as: Service"));
		
		virtual1_.event_goOnline();
		virtual2_.event_goOnline();
		
		try {
			this.wait( _TIMEOUT_ );
		} catch (InterruptedException e) {};
		
	}
	
	//To the same far ref 
	public synchronized void testMulticast100bigObj() throws Exception{
		ELActor vm1 = setUpActor(virtual1_);
		ELActor vm2   = setUpActor(virtual2_);
		setUpSuccessTrigger(vm2);
		
		vm1.sync_event_eval(
				NATParser.parse("DistributionTest#testMulticast()",
						"deftype Service; \n" +
					    "def bigObj := object: { \n" +
					    "   def a := 100; \n" +
					    "   def b := [1, 3, 5, \"hahaha\"]; \n" +
						"   def c[100] {20}; \n" +
						"}; \n" +
						"when: Service discovered: { | ref | \n" +
						"   def far[100] {ref}; \n" +
						"   send: (<-hello(1, bigObj)) toGroup: far; \n" +
						"}; \n" +
						""));
		
		vm2.sync_event_eval(
				NATParser.parse("DistributionTest#testMulticast()",
						"deftype Service; \n" +
					    "def ctr := 0; \n" +
						"def test := object: { \n" +
						"	def hello( i, obj ) { \n" +
						"                    ctr := (ctr + i); \n" +
						"                     if: (ctr = 100) then: { \n" +
						"                          success(); \n" +
						"                       }; \n" +
						"                  }; \n" +
						"}; \n" +
						"export: test as: Service"));
		
		virtual1_.event_goOnline();
		virtual2_.event_goOnline();
		
		try {
			this.wait( _TIMEOUT_ );
		} catch (InterruptedException e) {};
		
	}
	
	

}
